/*
 *      screen.c
 *
 *      Copyright 2007 Giulio Bai <giulio@hewle.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <sys.h>
#include <config.h>
#include <io2.h> /* also includes stringx.h and arg.h, which needs valist.h */
#include <video.h>
#include <os/shell.h>

/*
VGA color table: 16 bit for up to 16 different colors
-----------------------------------------------------
0  BLACK          8   DARK GREY
1  BLUE           9   LIGHT BLUE
2  GREEN          10  LIGHT GREEN
3  CYAN           11  LIGHT CYAN
4  RED            12  LIGHT RED
5  MAGENTA        13  LIGHT MAGENTA
6  BROWN          14  LIGHT BROWN
7  LIGHT GREY     15  WHITE
*/

/* These define our textpointer, our background and foreground
*  colors (attributes), and x and y cursor coordinates */
unsigned short *textmemptr;
int attrib = 0x0F;
int csr_x = 0, csr_y = 0;


/* Scrolls the screen */
void vid_scroll (void)
{
	unsigned blank, temp;

	/* A blank is defined as a space... we need to give it
	*  backcolor too */
	blank = 0x20 | (attrib << 8);

	/* Row 25 is the end, this means we need to scroll up */
	if (csr_y >= VID_HEIGHT) {
		/* Move the current text chunk that makes up the screen
		*  back in the buffer by a line */
		temp = csr_y - VID_HEIGHT + 1;
		memcpy ((unsigned char *)textmemptr, (const unsigned char *)(textmemptr + temp * VID_WIDTH), (VID_HEIGHT - temp) * VID_WIDTH * 2);

		/* Finally, we set the chunk of memory that occupies
		*  the last line of text to our 'blank' character */
		memsetw (textmemptr + (VID_HEIGHT - temp) * VID_WIDTH, blank, VID_WIDTH);
		csr_y = VID_HEIGHT - 1;
	}
}


/* Updates the hardware cursor: the little blinking line
*  on the screen under the last character pressed! */
void vid_move_cursor (void)
{
	unsigned where;

	/* The equation for finding the index in a linear
	*  chunk of memory can be represented by:
	*  Index = [(y * width) + x] */
	where = csr_y * VID_WIDTH + csr_x;

	/* This sends a command to indicies 14 and 15 in the
	*  CRT Control Register of the VGA controller. These
	*  are the high and low bytes of the index that show
	*  where the hardware cursor is to be 'blinking'. To
	*  learn more, you should look up some VGA specific
	*  programming documents. A great start to graphics:
	*  http://www.brackeen.com/home/vga */
	outportb (0x3D4, 14);
	outportb (0x3D5, where >> 8);
	outportb (0x3D4, 15);
	outportb (0x3D5, where);
}


/* Clears the screen */
void vid_clear (void)
{
	unsigned blank;
	int i;

	/* Again, we need the 'short' that will be used to
	*  represent a space with color */
	blank = 0x20 | (attrib << 8);

	/* Sets the entire screen to spaces in our current
	*  color */
	for (i=0; i<VID_HEIGHT; i ++) {
		memsetw (textmemptr + i * VID_WIDTH, blank, VID_WIDTH);
	}
	
	/* Update out virtual cursor, and then move the
	*  hardware cursor */
	csr_x = 0;
	csr_y = 0;
	
	vid_move_cursor ();
}


/* Puts a single character on the screen */
void putch (unsigned char c)
{
	unsigned short *where;
	unsigned att = attrib << 8;

	/* Handle a backspace, by moving the cursor back one space
	 * and deleting the previous char.
	 * If the shell prompt is displayed, be sure not to accidentally remove
	 * the prompt! */
	if (c == 0x08) {
		if (csr_x != 0 && csr_x > strlen (aShell.console.prompt)) {
			/* go back one space */
			csr_x --;
			
			/* delete char */
			putch (' ');
			csr_x --;
		}
		else {
			/* beeps */
		}
	}
	/* Handles a tab by incrementing the cursor's x, but only
	*  to a point that will make it divisible by 8 */
	else if (c == 0x09) {
		csr_x = (csr_x + TAB_WIDTH) & ~(TAB_WIDTH - 1);
	}
	/* Handles a 'Carriage Return', which simply brings the
	*  cursor back to the margin */
	else if (c == '\r') {
		csr_x = 0;
	}
	/* We handle our newlines the way DOS and the BIOS do: we
	*  treat it as if a 'CR' was also there, so we bring the
	*  cursor to the margin and we increment the 'y' value */
	else if (c == '\n') {	
		csr_x = 0;
		csr_y ++;	
		
		if (aShell.console.active) {
			char *c = NULL;
			
			if (c != NULL) {
				aShell.console.lastCmd = c;
				aShell.console.PComm (c);
			}
			
			aShell.console.NewLn ();
		}
	}
	/* Any character greater than and including a space, is a
	*  printable character. The equation for finding the index
	*  in a linear chunk of memory can be represented by:
	*  Index = [(y * width) + x] */
	else if (c >= ' ') {
		where = textmemptr + (csr_y * VID_WIDTH + csr_x);
		*where = c | att;	/* Character AND attributes: color */

		csr_x ++;
		
		
	}

	/* If the cursor has reached the edge of the screen's width, we
	*  insert a new line in there */
	if (csr_x >= VID_WIDTH) {
		csr_x = 0;
		csr_y ++;
	}
	
	/* Scroll the screen if needed, and finally move the cursor */
	vid_scroll ();
	vid_move_cursor ();
}


/* Sets the forecolor and backcolor that we will use */
void vid_set_color (unsigned char forecolor, unsigned char backcolor)
{
	/* Top 4 bytes are the background, bottom 4 bytes
	*  are the foreground color */
	attrib = (backcolor << 4) | (forecolor & 0x0F);
}

/* Resets text colors to default ones */
void vid_set_color_reset ()
{
	/* Use the default colors in config.h */
	vid_set_color (VID_DEF_FCOLOR, VID_DEF_BCOLOR);
}

int gotox (unsigned to_x)
{
	/* destination exists on the screen, move there */
	if (to_x > 0 && to_x < VID_WIDTH) {
		csr_x = to_x;
		
		return 1;
	}
	
	/* X could not be reached, return 0 as error */	
	return 0;
}


int gotoy (unsigned to_y)
{
	/* destination exists on the screen, move there */
	if (to_y > 0) {
		csr_y = to_y;
		
		return 1;
	}
	
	/* Y could not be reached, return 0 as error */	
	return 0;
}


int gotoxy (unsigned to_x, unsigned to_y)
{
	/* destination exists on the screen, move there */
	if (to_y > 0 && to_x > 0 && to_x < VID_WIDTH) {
		csr_x = to_x;
		csr_y = to_y;
		
		return 1;
	}
	
	/* X or Y could not be reached, return 0 as error */	
	return 0;
}



/* Sets our text-mode VGA pointer, then clears the screen for us */
void vid_init (void)
{
	/* location of video memory */
	textmemptr = (unsigned short *)0xB8000;
	vid_clear ();
	
	printi ("video support", 3, 1);
}
